package com.ruoyi.eqmonitor.utils;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.core.exception.ServiceException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

/**
 * IotDB sql构建器
 *
 * @see <a href="https://iotdb.apache.org/zh/UserGuide/V1.1.x/Query-Data/Overview.html">数据查询</a>
 */
@Slf4j
public class IotSqlBuilder {

    private WhereBuilder whereBuilder;

    private String[] columns;

    private String[] tableNames;

    private Boolean isLast;

    private String fillVal;

    private final StringBuilder groupBySql;

    private final StringBuilder havingSql;

    private final StringBuilder orderBySql;

    private final StringBuilder limitSql;

    public IotSqlBuilder() {
        this.columns = null;
        this.isLast = false;
        this.fillVal = null;
        this.groupBySql = new StringBuilder();
        this.havingSql = new StringBuilder();
        this.orderBySql = new StringBuilder();
        this.limitSql = new StringBuilder();
    }

    /**
     * 查询最新一条数据
     *
     * @return 表构建器
     */
    public IotSqlBuilder isLast() {
        this.isLast = true;
        return this;
    }

    /**
     * 填充值
     *
     * @param fillVal 填充值 可使用 FillType 枚举 或 BOOLEAN, INT32, INT64, FLOAT, DOUBLE, TEXT
     * @return 表构建器
     * @see FillType
     */
    public IotSqlBuilder fill(String fillVal) {
        this.fillVal = fillVal;
        return this;
    }

    /**
     * 查询所有字段
     *
     * @return 表构建器
     */
    public IotSqlBuilder selectAll() {
        return this.select("* ");
    }

    /**
     * 构建查询字段
     *
     * @param columns 列名
     * @return 表构建器
     */
    public IotSqlBuilder select(List<String> columns) {
        return this.select(ArrayUtil.toArray(columns, String.class));
    }

    /**
     * 构建查询字段
     *
     * @param columns 列名
     * @return 表构建器
     */
    public IotSqlBuilder select(String... columns) {
        if (ArrayUtil.isNotEmpty(this.columns)) {
            throw new ServiceException("查询字段只能设置一次");
        }
        this.columns = columns;
        return this;
    }

    /**
     * 构建表名
     *
     * @param tableNames 表名
     * @return where构建器
     */
    public IotSqlBuilder from(List<String> tableNames) {
        return this.from(ArrayUtil.toArray(tableNames, String.class));
    }

    /**
     * 构建表名
     *
     * @param tableNames 表名
     * @return where构建器
     */
    public IotSqlBuilder from(String... tableNames) {
        if (ArrayUtil.isNotEmpty(this.tableNames)) {
            throw new ServiceException("查询表只能设置一次");
        }
        this.tableNames = tableNames;
        return this;
    }

    public IotSqlBuilder where(WhereBuilder whereBuilder) {
        this.whereBuilder = whereBuilder;
        return this;
    }

    /**
     * 按时间维度分组 左开右闭
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @param interval  时间间隔 (ms, s, m, h, d, mo, y) 例如：1d
     * @return where构建器
     */
    public IotSqlBuilder groupByTime(Date startTime, Date endTime, String interval) {
        return this.groupByTime(startTime, endTime, true, false, interval, null);
    }

    /**
     * 按时间维度分组 左开右闭
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @param interval  时间间隔 (ms, s, m, h, d, mo, y) 例如：1d
     * @return where构建器
     */
    public IotSqlBuilder groupByTime(Date startTime, Date endTime, String interval, String step) {
        return this.groupByTime(startTime, endTime, true, false, interval, step);
    }

    /**
     * 按时间维度分组 自定义开闭
     *
     * @param startTime    开始时间
     * @param endTime      结束时间
     * @param includeLeft  是否包含左边界
     * @param includeRight 是否包含右边界
     * @param interval     时间间隔 (ms, s, m, h, d, mo, y) 例如：1d
     * @param step         步长
     * @return where构建器
     */
    public IotSqlBuilder groupByTime(Date startTime, Date endTime, Boolean includeLeft, Boolean includeRight, String interval) {
        return this.groupByTime(startTime, endTime, includeLeft, includeRight, interval, null);
    }

    /**
     * 按时间维度分组 自定义开闭
     *
     * @param startTime    开始时间
     * @param endTime      结束时间
     * @param includeLeft  是否包含左边界
     * @param includeRight 是否包含右边界
     * @param interval     时间间隔 (ms, s, m, h, d, mo, y) 例如：1d
     * @param step         步长
     * @return where构建器
     */
    public IotSqlBuilder groupByTime(Date startTime, Date endTime, Boolean includeLeft, Boolean includeRight, String interval, String step) {
        if (includeLeft == null) {
            includeLeft = true;
        }
        if (includeRight == null) {
            includeRight = false;
        }
        if (includeLeft.equals(includeRight)) {
            throw new ServiceException("开闭区间两端不允许同时开或同时闭");
        }
        if (this.groupBySql.length() == 0) {
            this.groupBySql.append(" GROUP BY ");
        } else {
            this.groupBySql.append(", ");
        }
        this.groupBySql.append("( ")
                .append(includeLeft ? "[" : "(")
                .append(startTime.getTime())
                .append(",")
                .append(endTime.getTime())
                .append(includeRight ? "]" : ")")
                .append(",")
                .append(interval);
        if (StrUtil.isNotBlank(step)) {
            this.groupBySql.append(",").append(step);
        }
        this.groupBySql.append(" )");
        return this;
    }

    /**
     * 按字段分组
     *
     * @param columns 字段名
     * @return where构建器
     */
    public IotSqlBuilder groupBy(List<String> columns) {
        return this.groupBy(ArrayUtil.toArray(columns, String.class));
    }

    /**
     * 按字段分组
     *
     * @param columns 字段名
     * @return where构建器
     */
    public IotSqlBuilder groupBy(String... columns) {
        if (this.groupBySql.length() == 0) {
            this.groupBySql.append(" GROUP BY ");
        } else {
            this.groupBySql.append(", ");
        }
        this.groupBySql.append(ArrayUtil.join(columns, ", "));
        return this;
    }

    /**
     * 聚合条件
     *
     * @param conditions 条件表达式
     * @return where构建器
     */
    public IotSqlBuilder having(List<String> conditions) {
        return this.having(ArrayUtil.toArray(conditions, String.class));
    }

    /**
     * 聚合条件
     *
     * @param conditions 条件表达式
     * @return where构建器
     */
    public IotSqlBuilder having(String... conditions) {
        if (this.havingSql.length() == 0) {
            this.havingSql.append(" HAVING ");
        } else {
            this.havingSql.append(" AND ");
        }
        this.havingSql.append(ArrayUtil.join(conditions, " AND "));
        return this;
    }

    /**
     * 按字段升序排序
     *
     * @param columns 字段名
     * @return where构建器
     */
    public IotSqlBuilder orderByAsc(List<String> columns) {
        return this.orderByAsc(ArrayUtil.toArray(columns, String.class));
    }

    /**
     * 按字段升序排序
     *
     * @param columns 字段名
     * @return where构建器
     */
    public IotSqlBuilder orderByAsc(String... columns) {
        if (this.orderBySql.indexOf(" ORDER BY ") != -1) {
            this.orderBySql.append(" ORDER BY ");
        } else {
            this.orderBySql.append(", ");
        }
        this.orderBySql.append(ArrayUtil.join(columns, ", "));
        return this;
    }

    /**
     * 按字段降序排序
     *
     * @param columns 字段名
     * @return where构建器
     */
    public IotSqlBuilder orderByDesc(List<String> columns) {
        return this.orderByDesc(ArrayUtil.toArray(columns, String.class));
    }

    /**
     * 按字段降序排序
     *
     * @param column 字段名
     * @return where构建器
     */
    public IotSqlBuilder orderByDesc(String... columns) {
        if (this.orderBySql.indexOf(" ORDER BY ") == -1) {
            this.orderBySql.append(" ORDER BY ");
        } else {
            this.orderBySql.append(", ");
        }
        this.orderBySql.append(ArrayUtil.join(columns, " DESC, ")).append(" DESC");
        return this;
    }

    /**
     * 根据偏移量分页
     *
     * @param offset   偏移量
     * @param pageSize 页大小
     * @return where构建器
     */
    public IotSqlBuilder limit(int offset, int pageSize) {
        if (this.limitSql.length() > 0) {
            throw new ServiceException("分页条件只能设置一次");
        }
        this.limitSql.append(" LIMIT ").append(offset).append(", ").append(pageSize);
        return this;
    }

    /**
     * 根据页码分页
     *
     * @param pageNo   页码
     * @param pageSize 页大小
     * @return where构建器
     */
    public IotSqlBuilder page(int pageNo, int pageSize) {
        if (this.limitSql.length() > 0) {
            throw new ServiceException("分页条件只能设置一次");
        }
        int offset = (pageNo - 1) * pageSize;
        this.limitSql.append(" LIMIT ").append(offset).append(", ").append(pageSize);
        return this;
    }

    /**
     * 构建sql
     *
     * @return sql
     */
    public String build() {
        return this.build(null);
    }

    /**
     * 构建sql
     *
     * @return sql
     */
    public String build(String prefix) {
        if (ArrayUtil.isEmpty(this.columns)) {
            throw new ServiceException("查询字段不能为空");
        }
        if (ArrayUtil.isEmpty(this.tableNames)) {
            throw new ServiceException("查询表不能为空");
        }
        String[] tableNames = this.tableNames;
        if (StrUtil.isNotBlank(prefix)) {
            tableNames = new String[this.tableNames.length];
            for (int i = 0; i < this.tableNames.length; i++) {
                tableNames[i] = prefix.concat(".").concat(this.tableNames[i]);
            }
        }
        StringBuilder sql = new StringBuilder();
        // SELECT LAST [ ...columns ]
        sql.append("SELECT ")
                .append(this.isLast ? "LAST " : StrUtil.EMPTY)
                .append(ArrayUtil.join(this.columns, ", "));

        // FROM [ ...tableNames ]
        sql.append(" FROM ")
                .append(ArrayUtil.join(tableNames, ", "));

        // WHERE [ ...conditions ]
        String whereSql = this.whereBuilder != null ? this.whereBuilder.build() : StrUtil.EMPTY;
        if (!whereSql.isEmpty()) {
            sql.append(" WHERE ").append(whereSql);
        }

        // GROUP BY [ ...columns ]
        if (this.groupBySql.length() > 0) {
            sql.append(this.groupBySql);
        }
        // HAVING [ ...conditions ]
        if (this.havingSql.length() > 0) {
            sql.append(this.havingSql);
        }
        // ORDER BY [ ...columns ]
        if (this.orderBySql.length() > 0) {
            sql.append(this.orderBySql);
        }
        // FILL [ fillVal ] -- 测试时放在limit后边会报错, 所以放在limit之前
        if (StrUtil.isNotBlank(this.fillVal)) {
            sql.append(" FILL(").append(this.fillVal).append(")");
        }
        // LIMIT [ offset, pageSize ]
        if (this.limitSql.length() > 0) {
            sql.append(this.limitSql);
        }
        log.debug("iotdb sql: {}", sql);
        return sql.toString();
    }

    /**
     * lambda + build 构建where条件 用于查询
     */
    public static class WhereBuilder {

        private final StringBuilder sql;

        public WhereBuilder() {
            this.sql = new StringBuilder();
        }

        private String build() {
            return this.sql.toString();
        }

        /**
         * 等于
         *
         * @param column 字段名
         * @param value  值
         * @return where构建器
         */
        public WhereBuilder eq(String column, Object value) {
            this.beforeAddCondition();
            value = this.convertValue(value);
            this.sql.append(column).append(" = ").append(value);
            return this;
        }

        /**
         * 不等于
         *
         * @param column 字段名
         * @param value  值
         * @return where构建器
         */
        public WhereBuilder ne(String column, Object value) {
            this.beforeAddCondition();
            value = this.convertValue(value);
            this.sql.append(column).append(" != ").append(value);
            return this;
        }

        /**
         * 大于
         *
         * @param column 字段名
         * @param value  值
         * @return where构建器
         */
        public WhereBuilder gt(String column, Object value) {
            this.beforeAddCondition();
            value = this.convertValue(value);
            this.sql.append(column).append(" > ").append(value);
            return this;
        }

        /**
         * 小于
         *
         * @param column 字段名
         * @param value  值
         * @return where构建器
         */
        public WhereBuilder lt(String column, Object value) {
            this.beforeAddCondition();
            value = this.convertValue(value);
            this.sql.append(column).append(" < ").append(value);
            return this;
        }

        /**
         * 大于等于
         *
         * @param column 字段名
         * @param value  值
         * @return where构建器
         */
        public WhereBuilder ge(String column, Object value) {
            this.beforeAddCondition();
            value = this.convertValue(value);
            this.sql.append(column).append(" >= ").append(value);
            return this;
        }

        /**
         * 小于等于
         *
         * @param column 字段名
         * @param value  值
         * @return where构建器
         */
        public WhereBuilder le(String column, Object value) {
            this.beforeAddCondition();
            value = this.convertValue(value);
            this.sql.append(column).append(" <= ").append(value);
            return this;
        }

        /**
         * 全模糊查询
         *
         * @param column 字段名
         * @param value  值
         * @return where构建器
         */
        public WhereBuilder like(String column, String value) {
            this.beforeAddCondition();
            this.sql.append(column).append(" LIKE ").append("%").append(value).append("%");
            return this;
        }

        /**
         * 左模糊查询
         *
         * @param column 字段名
         * @param value  值
         * @return where构建器
         */
        public WhereBuilder likeLeft(String column, String value) {
            this.beforeAddCondition();
            this.sql.append(column).append(" LIKE ").append("%").append(value);
            return this;
        }

        /**
         * 右模糊查询
         *
         * @param column 字段名
         * @param value  值
         * @return where构建器
         */
        public WhereBuilder likeRight(String column, String value) {
            this.beforeAddCondition();
            this.sql.append(column).append(" LIKE ").append(value).append("%");
            return this;
        }

        /**
         * 在列表
         *
         * @param column 字段名
         * @param value  值
         * @return where构建器
         */
        public WhereBuilder in(String column, Object... value) {
            this.beforeAddCondition();
            this.sql.append(column).append(" IN ").append(ArrayUtil.join(value, ", "));
            return this;
        }

        /**
         * 不在列表
         *
         * @param column 字段名
         * @param value  值
         * @return where构建器
         */
        public WhereBuilder notIn(String column, Object... value) {
            this.beforeAddCondition();
            this.sql.append(column).append(" NOT IN ").append(ArrayUtil.join(value, ", "));
            return this;
        }

        /**
         * 为空
         *
         * @param column 字段名
         * @return where构建器
         */
        public WhereBuilder isNull(String column) {
            this.beforeAddCondition();
            this.sql.append(column).append(" IS NULL");
            return this;
        }

        /**
         * 不为空
         *
         * @param column 字段名
         * @return where构建器
         */
        public WhereBuilder isNotNull(String column) {
            this.beforeAddCondition();
            this.sql.append(column).append(" IS NOT NULL");
            return this;
        }

        /**
         * 两个值之间
         *
         * @param column 字段名
         * @param value1 值1
         * @param value2 值2
         * @return where构建器
         */
        public WhereBuilder between(String column, Object value1, Object value2) {
            this.beforeAddCondition();
            value1 = this.convertValue(value1);
            value2 = this.convertValue(value2);
            this.sql.append(column).append(" BETWEEN ").append(value1).append(" AND ").append(value2);
            return this;
        }

        /**
         * 开始与结束时间之间
         *
         * @param startTime 开始时间
         * @param endTime   结束时间
         * @return where构建器
         */
        public WhereBuilder betweenTime(Date startTime, Date endTime) {
            return betweenTime(startTime.getTime(), endTime.getTime());
        }

        /**
         * 开始与结束时间之间
         *
         * @param startTime 开始时间
         * @param endTime   结束时间
         * @return where构建器
         */
        public WhereBuilder betweenTime(long startTime, long endTime) {
            return this.ge("time", startTime).le("time", endTime);
        }

        /**
         * 不在两个值之间
         *
         * @param column 字段名
         * @param value1 值1
         * @param value2 值2
         * @return where构建器
         */
        public WhereBuilder notBetween(String column, Object value1, Object value2) {
            this.beforeAddCondition();
            value1 = this.convertValue(value1);
            value2 = this.convertValue(value2);
            this.sql.append(column).append(" NOT BETWEEN ").append(value1).append(" AND ").append(value2);
            return this;
        }

        /**
         * or 条件
         *
         * @param condition or右侧的条件
         * @return where构建器
         */
        public WhereBuilder or(WhereBuilder condition) {
            if (this.sql.length() <= 0) {
                throw new ServiceException("or左侧条件不能为空");
            }
            if (condition.sql.length() <= 0) {
                throw new ServiceException("or右侧条件不能为空");
            }
            this.sql.insert(0, "(");
            this.sql.append(")");

            this.sql.append(condition.build());
            this.sql.append(" OR ");
            return this;
        }

        private void beforeAddCondition() {
            if (this.sql.length() > 0) {
                this.sql.append(" AND ");
            }
        }

        private Object convertValue(Object value) {
            if (value instanceof Date) {
                value = ((Date) value).getTime();
            } else if (value instanceof LocalDate) {
                value = ((LocalDate) value).toEpochDay();
            } else if (value instanceof LocalDateTime) {
                value = ((LocalDateTime) value).toInstant(java.time.ZoneOffset.of("+8")).toEpochMilli();
            }
            return value;
        }

    }

    /**
     * 填充类型
     */
    @Getter
    public static enum FillType {
        /**
         * 使用该列前一个非空值进行填充
         */
        PREVIOUS("PREVIOUS"),
        /**
         * 使用该列前一个非空值和下一个非空值的线性插值进行填充
         */
        LINEAR("LINEAR");

        private final String type;

        private FillType(String type) {
            this.type = type;
        }

    }
}
